/*
 * This file is part of Applied Energistics 2. Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. Applied
 * Energistics 2 is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version. Applied Energistics 2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
 * Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
 * Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
 */

package appeng.core;

import javax.annotation.Nonnull;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;

import appeng.core.features.AEFeature;
import appeng.tile.AEBaseTile;
import appeng.util.Platform;

/**
 * Utility class for easier logging.
 */
public final class AELog {

    private static final String LOGGER_PREFIX = "AE2:";
    private static final String SERVER_SUFFIX = "S";
    private static final String CLIENT_SUFFIX = "C";

    private static final String BLOCK_UPDATE = "Block Update of %s @ ( %d, %d, %d )";

    private static final String DEFAULT_EXCEPTION_MESSAGE = "Exception: ";

    private AELog() {}

    /**
     * Returns a {@link Logger} logger suitable for the effective side (client/server).
     *
     * @return a suitable logger instance
     */
    private static Logger getLogger() {
        final String loggerName = LOGGER_PREFIX + (Platform.isServer() ? SERVER_SUFFIX : CLIENT_SUFFIX);
        final Logger logger = LogManager.getLogger(loggerName);

        return logger;
    }

    /**
     * Indicates of the global log is enabled or disabled.
     * <p>
     * By default it is enabled.
     *
     * @return true when the log is enabled.
     */
    public static boolean isLogEnabled() {
        return AEConfig.instance == null || AEConfig.instance.isFeatureEnabled(AEFeature.Logging);
    }

    /**
     * Logs an unformatted message with a specific log level.
     * <p>
     * The output can be globally disabled via the configuration file.
     *
     * @param level   the intended level.
     * @param message the message to be printed.
     */
    public static void logSimple(@Nonnull final Level level, @Nonnull final String message) {
        if (AELog.isLogEnabled()) {
            final Logger logger = getLogger();

            logger.log(level, message);
        }
    }

    /**
     * Logs a formatted message with a specific log level.
     * <p>
     * This uses {@link String#format(String, Object...)} as opposed to the {@link ParameterizedMessage} to allow a more
     * flexible formatting.
     * <p>
     * The output can be globally disabled via the configuration file.
     *
     * @param level   the intended level.
     * @param message the message to be formatted.
     * @param params  the parameters used for {@link String#format(String, Object...)}.
     */
    public static void log(@Nonnull final Level level, @Nonnull final String message, final Object... params) {
        if (AELog.isLogEnabled()) {
            final String formattedMessage = String.format(message, params);
            final Logger logger = getLogger();

            logger.log(level, formattedMessage);
        }
    }

    /**
     * Log an exception with a custom message formated via {@link String#format(String, Object...)}
     * <p>
     * Similar to {@link AELog#log(Level, String, Object...)}.
     *
     * @param level     the intended level.
     * @param exception
     * @param message   the message to be formatted.
     * @param params    the parameters used for {@link String#format(String, Object...)}.
     * @see AELog#log(Level, String, Object...)
     */
    public static void log(@Nonnull final Level level, @Nonnull final Throwable exception, @Nonnull String message,
            final Object... params) {
        if (AELog.isLogEnabled()) {
            final String formattedMessage = String.format(message, params);
            final Logger logger = getLogger();

            logger.log(level, formattedMessage, exception);
        }
    }

    /**
     * @param format
     * @param params
     * @see AELog#log(Level, String, Object...)
     */
    public static void info(@Nonnull final String format, final Object... params) {
        log(Level.INFO, format, params);
    }

    /**
     * Log exception as {@link Level#INFO}
     *
     * @param exception
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void info(@Nonnull final Throwable exception) {
        log(Level.INFO, exception, DEFAULT_EXCEPTION_MESSAGE);
    }

    /**
     * Log exception as {@link Level#INFO}
     *
     * @param exception
     * @param message
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void info(@Nonnull final Throwable exception, @Nonnull final String message) {
        log(Level.INFO, exception, message);
    }

    /**
     * @param format
     * @param params
     * @see AELog#log(Level, String, Object...)
     */
    public static void warn(@Nonnull final String format, final Object... params) {
        log(Level.WARN, format, params);
    }

    /**
     * Log exception as {@link Level#WARN}
     *
     * @param exception
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void warn(@Nonnull final Throwable exception) {
        log(Level.WARN, exception, DEFAULT_EXCEPTION_MESSAGE);
    }

    /**
     * Log exception as {@link Level#WARN}
     *
     * @param exception
     * @param message
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void warn(@Nonnull final Throwable exception, @Nonnull final String message) {
        log(Level.WARN, exception, message);
    }

    /**
     * @param format
     * @param params
     * @see AELog#log(Level, String, Object...)
     */
    public static void error(@Nonnull final String format, final Object... params) {
        log(Level.ERROR, format, params);
    }

    /**
     * Log exception as {@link Level#ERROR}
     *
     * @param exception
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void error(@Nonnull final Throwable exception) {
        log(Level.ERROR, exception, DEFAULT_EXCEPTION_MESSAGE);
    }

    /**
     * Log exception as {@link Level#ERROR}
     *
     * @param exception
     * @param message
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void error(@Nonnull final Throwable exception, @Nonnull final String message) {
        log(Level.ERROR, exception, message);
    }

    /**
     * Log message as {@link Level#DEBUG}
     *
     * @param format
     * @param data
     * @see AELog#log(Level, String, Object...)
     */
    public static void debug(@Nonnull final String format, final Object... data) {
        if (AELog.isDebugLogEnabled()) {
            log(Level.DEBUG, format, data);
        }
    }

    /**
     * Log exception as {@link Level#DEBUG}
     *
     * @param exception
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void debug(@Nonnull final Throwable exception) {
        if (AELog.isDebugLogEnabled()) {
            log(Level.DEBUG, exception, DEFAULT_EXCEPTION_MESSAGE);
        }
    }

    /**
     * Log exception as {@link Level#DEBUG}
     *
     * @param exception
     * @param message
     * @see AELog#log(Level, Throwable, String, Object...)
     */
    public static void debug(@Nonnull final Throwable exception, @Nonnull final String message) {
        if (AELog.isDebugLogEnabled()) {
            log(Level.DEBUG, exception, message);
        }
    }

    /**
     * Use to check for an enabled debug log.
     * <p>
     * Can be used to prevent the execution of debug logic.
     *
     * @return true when the debug log is enabled.
     */
    public static boolean isDebugLogEnabled() {
        return AEConfig.instance.isFeatureEnabled(AEFeature.DebugLogging);
    }

    //
    // Specialized handlers
    //

    /**
     * A specialized logging for grinder recipes, can be disabled inside configuration file.
     *
     * @param message String to be logged
     */
    public static void grinder(@Nonnull final String message) {
        if (AEConfig.instance.isFeatureEnabled(AEFeature.GrinderLogging)) {
            log(Level.DEBUG, "grinder: " + message);
        }
    }

    /**
     * A specialized logging for mod integration errors, can be disabled inside configuration file.
     *
     * @param exception
     */
    public static void integration(@Nonnull final Throwable exception) {
        if (AEConfig.instance.isFeatureEnabled(AEFeature.IntegrationLogging)) {
            debug(exception);
        }
    }

    /**
     * Logging of block updates.
     * <p>
     * Off by default, can be enabled inside the configuration file.
     *
     * @param xCoord
     * @param yCoord
     * @param zCoord
     * @param aeBaseTile
     * @see AELog#log(Level, String, Object...)
     */
    public static void blockUpdate(final int xCoord, final int yCoord, final int zCoord,
            @Nonnull final AEBaseTile aeBaseTile) {
        if (AEConfig.instance.isFeatureEnabled(AEFeature.UpdateLogging)) {
            info(BLOCK_UPDATE, aeBaseTile.getClass().getName(), xCoord, +yCoord, +zCoord);
        }
    }

    /**
     * Use to check for an enabled crafting log.
     * <p>
     * Can be used to prevent the execution of unneeded logic.
     *
     * @return true when the crafting log is enabled.
     */
    public static boolean isCraftingLogEnabled() {
        return AEConfig.instance.isFeatureEnabled(AEFeature.CraftingLog);
    }

    /**
     * Logging for autocrafting.
     * <p>
     * Off by default, can be enabled inside the configuration file.
     *
     * @param message
     * @param params
     * @see AELog#log(Level, String, Object...)
     */
    public static void crafting(@Nonnull final String message, final Object... params) {
        if (AELog.isCraftingLogEnabled()) {
            log(Level.INFO, message, params);
        }
    }

    /**
     * Use to check for an enabled crafting debug log.
     * <p>
     * Can be used to prevent the execution of unneeded logic.
     *
     * @return true when the crafting debug log is enabled.
     */
    public static boolean isCraftingDebugLogEnabled() {
        return AEConfig.instance.isFeatureEnabled(AEFeature.CraftingLog)
                && AEConfig.instance.isFeatureEnabled(AEFeature.DebugLogging);
    }

    /**
     * Debug logging for autocrafting.
     * <p>
     * Off by default, can be enabled inside the configuration file.
     *
     * @param message
     * @param params
     * @see AELog#log(Level, String, Object...)
     */
    public static void craftingDebug(@Nonnull final String message, final Object... params) {
        if (AELog.isCraftingDebugLogEnabled()) {
            log(Level.DEBUG, message, params);
        }
    }

    /**
     * Print current stack trace for debug purposes
     *
     * @param level Log level
     */
    public static void printStackTrace(Level level) {
        log(level, "Stack trace:");
        for (StackTraceElement traceElement : Thread.currentThread().getStackTrace())
            log(level, "\t " + traceElement.toString());
    }
}
